package enums

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"reflect"
	"strings"
)

type IntConstraint interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 |
		~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}

type EnumConst[T IntConstraint] struct {
	Value      T
	Annotation string
	Name       string
	Tran       string // translation
}

// GenerationEnumsCode 生成枚举
func GenerationEnumsCode[T IntConstraint](filename, enumName string, enumsConst []EnumConst[T]) error {
	builder := strings.Builder{}

	// Deal with some possible folder naming formats
	absPath, err := filepath.Abs(filename)
	if err != nil {
		return err
	}
	builder.WriteString(fmt.Sprintf("package %s \n\n",
		strings.ReplaceAll(filepath.Base(filepath.Dir(absPath)), "-", "_")))

	// get type kinds name
	var enumType T
	typeKindName := reflect.TypeOf(enumType).String()

	// generation enum type kinds
	builder.WriteString(genEnumType[T](enumName, typeKindName))

	// generation const
	enumConst, err := genEnumConst[T](enumsConst, enumName)
	if err != nil {
		return err
	}
	builder.WriteString(enumConst)

	// generation String() func
	builder.WriteString(genEnumStringFunc(enumName, enumsConst))

	return writeGoCodeToFile(
		strings.Join([]string{filename, ".go"}, ""),
		builder.String(),
	)
}

func genEnumConst[T IntConstraint](enumsConst []EnumConst[T], enumName string) (string, error) {
	if len(enumsConst) < 1 {
		return "", errors.New("enumsConst num lt 1")
	}
	builder := strings.Builder{}

	/*
		generation annotation and const
	*/
	for i := range enumsConst {
		if i == 0 {
			builder.WriteString("const ( \n")
		}

		builder.WriteString(strings.Join([]string{
			func() string {
				if enumsConst[i].Annotation == "" {
					return ""
				}
				return fmt.Sprintf("%s %s %s %s", "//", enumsConst[i].Name, enumsConst[i].Annotation, "\n")
			}(),
			enumsConst[i].Name, enumName, "=", fmt.Sprintf("%v", enumsConst[i].Value), "\n",
		}, " "))
	}
	builder.WriteString(" ) \n\n")
	return builder.String(), nil
}

func genEnumType[T IntConstraint](enumName, enumType string) string {
	return strings.Join([]string{"type", enumName, enumType, "\n\n"}, " ")
}

func genEnumStringFunc[T IntConstraint](enumName string, enumsConst []EnumConst[T]) string {
	builder := strings.Builder{}
	builder.WriteString(fmt.Sprintf("func (s %s ) String() string { \n switch s {\n", enumName))
	// generation case
	for i := range enumsConst {
		builder.WriteString(fmt.Sprintf("case %s:\n return \"%s\" \n", enumsConst[i].Name, func(c EnumConst[T]) string {
			if c.Tran == "" {
				return c.Name
			}
			return c.Tran
		}(enumsConst[i])))
	}
	builder.WriteString("default:\n return \"<nil>\"}\n}\n\n")
	return builder.String()
}

func writeGoCodeToFile(filename, code string) error {
	file, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer file.Close()

	_, err = file.WriteString(code)
	if err != nil {
		return err
	}

	fmt.Println("Go code has been written to", filename)
	return nil
}
